/**
 * {type:req, reqID:<reqID>, fname:<fname>, args:<args>} ->  remote
 * remote -> (type:resp, reqID:<reqID>, res:<result>, err:<str>)
 */
const __onMessage = (data, reqMap, env, com) => {
    let type = data.type;
    let reqID = data.reqID;
    if (type === 'req') {
        let fname = data.fname;
        let args = data.args;
        let func = env[fname];
        try {
            let r = func(...args);
            com.postMessage({ type: 'resp', reqID: reqID, res: r });
        } catch (ex) {
            com.postMessage({
                type: 'resp',
                reqID: reqID,
                err: '' + ex
            });
        }
    } else if (type === 'resp') {
        let task = reqMap[reqID];
        if (!task) {
            console.warn('resp timeout!');
            return;
        } else {
            delete reqMap[reqID];
            let err = data.err;
            if (err) {
                task.rej(err);
            } else {
                task.res(data.res);
            }
        }
    }
};

const __proxy = (reqMap, com, ticker) => {
    let reqID = 0;
    let pObj = new Proxy(new Object(), {
        get(target, key) {
            return (...args) => {
                return new Promise((res, rej) => {
                    reqID++;
                    reqMap[reqID] = {
                        res,
                        rej,
                        tick: ticker.tick
                    };
                    com.postMessage({
                        type: 'req',
                        reqID: reqID,
                        fname: key,
                        args: args
                    });
                });
            };
        }
    });
    return pObj;
};

let __checkTimeout = (reqMap, ticker) => {
    if (!ticker.timeout || ticker.timeout < 1) ticker.timeout = 100;
    let timeID = setInterval(() => {
        let timeoutList = [];
        for (let reqID in reqMap) {
            let req = reqMap[reqID];
            let tick = req.tick;
            if (tick < ticker.tick) timeoutList.push([reqID, req]); //{0:reqID, 1:req}
        }
        for (let reqPair of timeoutList) {
            reqPair[1].rej('timeout exception');
            delete reqMap[reqPair[0]];
        }
        ticker.tick++;
    }, ticker.timeout);
    return timeID;
};

if (!self.window) {
    let reqMap = {}; //custom it
    self.onmessage = msg => __onMessage(msg.data, reqMap, self.exports, self);
    let ticker = {
        tick: 0,
        get timeout() {
            return self.timeout;
        }
    };
    self.remote = __proxy(reqMap, self, ticker);
    setTimeout(function() {
        __checkTimeout(reqMap, ticker);
    }, 0);
} else {
    self.WorkerProxy = class {
        constructor(script, timeout) {
            this.reqMap = {};
            this.worker = new Worker(script);
            this.ticker = {
                tick: 0,
                timeout: timeout
            };
            this.worker.onmessage = msg =>
                __onMessage(msg.data, this.reqMap, this.exports, this.worker);
            this.remote = __proxy(this.reqMap, this.worker, this.ticker);
            this.exports = {};
            this.timeID = __checkTimeout(this.reqMap, this.ticker);
            this.dispose = () => {
                this.worker.terminate();
                clearInterval(this.timeID);
                this.remote = () => {
                    console.error('worker is disposed');
                };
            };
        }
    };
}
